//
// Created by jk101 on 2023/12/11.
//
#ifndef BLOCKQUEUE_H
#define BLOCKQUEUE_H

#include <mutex>
#include <deque>
#include <condition_variable>
#include <assert.h>
#include <chrono>

/**
 * 阻塞队列，初始化时指定容量，默认1024。
 * 当队列无剩余空间时，向其写入元素将阻塞。当队列无元素时，从中获取元素将阻塞。
 * @tparam T 泛型，用于指定元素类型
 */
template<class T>
class BlockQueue {
public:
    explicit BlockQueue(size_t maxCapacity = 1024);

    /**
     * 析构函数，当delete此对象时，将会清理队列中的元素，并唤醒所有阻塞线程
     */
    ~BlockQueue();

    /**
     * 析构函数的实现，调用此方法将清空队列，并唤醒所有阻塞线程，若元素需要释放内存，应先遍历一遍释放内存
     */
    void close();

    /**
     * 刷新，在流api中常常用于表示将缓冲区内容发送出去，这里用于表示通知消费者队列可能有新的元素
     */
    void flush();

    /**
     * 清空队列
     */
    void clear();

    /**
     * 查看队列的第一个元素
     */
    T front();

    /**
     * 查看队列的最后一个元素
     */
    T back();

    /**
     * 获取队列当前的元素数量
     */
    size_t get_size();

    /**
     * 获取队列容量
     */
    size_t get_capacity();

    /**
     * 向队列末尾添加新元素
     * @param item 元素引用
     * 在C++中，按值传递：方法的参数传递会将实参复制作为形参使用，这导致需要额外的内存与性能消耗。
     * &表示按引用传递，对形参的修改就是对实参的修改，对形参的赋值可以修改实参的指向新的内存地址。
     */
    void push_back(const T& item);

    /**
     * 向队列前面添加新元素
     * @param item 元素引用
     */
    void push_front(const T& item);

    /**
     * 判断队列是否为空
     */
    bool empty();

    /**
     * 判断队列是否已满
     * @return
     */
    bool full();

    /**
     * 取出队列第一个元素
     * @param item 元素引用，这里就是通过
     * @return
     */
    bool pop(T& item);

    bool pop(T& item, int timeout);

private:
    std::deque<T> deq;

    size_t capacity;

    std::mutex mtx;

    bool isClose;

    std::condition_variable Consumer;

    std::condition_variable Producer;
};


template<class T>
BlockQueue<T>::BlockQueue(size_t maxCapacity):capacity(maxCapacity)
{
    assert(maxCapacity > 0);
    isClose = false;
}

template<class T>
BlockQueue<T>::~BlockQueue()
{
    close();
};

template<class T>
void BlockQueue<T>::close()
{
    {
        std::lock_guard<std::mutex> locker(mtx);
        deq.clear();
        isClose= true;
    }
    Producer.notify_all();
    Consumer.notify_all();
};

template<class T>
void BlockQueue<T>::flush()
{
    Consumer.notify_one();
}

template<class T>
void BlockQueue<T>::clear()
{
    std::lock_guard<std::mutex> lock(mtx);
    deq.clear();
}

template<class T>
T BlockQueue<T>::front()
{
    std::lock_guard<std::mutex> lock(mtx);
    return deq.front();
}

template<class T>
T BlockQueue<T>::back()
{
    std::lock_guard<std::mutex> lock(mtx);
    return deq.back();
}

template<class T>
size_t BlockQueue<T>::get_size()
{
    std::lock_guard<std::mutex> lock(mtx);
    return deq.size();
}

template<class T>
size_t BlockQueue<T>::get_capacity()
{
    std::lock_guard<std::mutex> lock(mtx);
    return capacity;
}

template<class T>
void BlockQueue<T>::push_back(const T& item)
{
    std::unique_lock<std::mutex> lock(mtx);
    while (deq.size() >= capacity)
    {
        Producer.wait(lock);
    }
    deq.push_back(item);
    Consumer.notify_one();
}

template<class T>
void BlockQueue<T>::push_front(const T& item)
{
    std::unique_lock<std::mutex> lock(mtx);
    while (deq.size() >= capacity)
    {
        Producer.wait(lock);
    }
    deq.push_front(item);
    Consumer.notify_one();
}

template<class T>
bool BlockQueue<T>::empty()
{
    std::lock_guard<std::mutex> lock(mtx);
    return deq.empty();
}

template<class T>
bool BlockQueue<T>::full()
{
    std::lock_guard<std::mutex> lock(mtx);
    return deq.size()>=capacity;
}

template<class T>
bool BlockQueue<T>::pop(T& item)
{
    std::unique_lock<std::mutex> lock(mtx);
    while (deq.empty())
    {
        Consumer.wait(lock);
        if (isClose)
            return false;
    }
    LOGI("get_size %zu",deq.size());
    item = deq.front();
    deq.pop_front();
    Producer.notify_one();
    return true;
}

template<class T>
bool BlockQueue<T>::pop(T& item,int timeout)
{
    std::unique_lock<std::mutex> lock(mtx);
    while (deq.empty())
    {
        if (Consumer.wait_for(lock, std::chrono::microseconds(timeout)) == std::cv_status::timeout)
            return false;
        if (isClose)
            return false;
    }
    item = deq.front();
    deq.pop_front();
    Producer.notify_one();
    return true;
}

#endif // BLOCKQUEUE_H